package com.blandware.atleap.webapp.taglib.core.logic;

import com.blandware.atleap.common.util.ConvertUtil;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.apache.struts.taglib.TagUtils;

/**
 * <p>Replacement for JSTL ForEach tag with the same functionality.<br />
 * The only reason of creation this tag is that there are problems of combined usages of tags, which conform to different
 * JSP specifications (1.2 and 2.0). For example there were problems while running on IBM WebSphere when Atleap
 * simple tag handlers have been used inside JSTL ForEach tag. To avoid such problems, we have created our own iteration tag
 * </p>
 * <p>This tag provides an ability to export result of each iteration to scope other than page. It's usable, for example, when
 * some JSP is included between opening and closing iterator tags and needs to gain access to iterated items</p>
 * <p>
 * Before iteration <b>items</b> is converted to <code>java.util.List</code>
 * (if <b>items</b> is not given or <code>null</code>, then resulting list
 * contains <code>Integer</code>s with values from <b>begin</b> to <b>end</b>
 * with step equal to <b>step</b>). Then iteration is run over
 * that list: from <b>begin</b> index to <b>end</b> index with <b>step</b>.
 * On each iteration, iteration status will be exported to <b>statusVar</b>,
 * current object will be exported to <b>var</b> and body will be rendered.
 * </p>
 * <p>
 * Full list of allowed attributes:
 * <ul>
 * <li>
 * <b>items</b> - "collection" of items to iterate over. See
 * {@link com.blandware.atleap.common.util.ConvertUtil#convertCollectionToList(Object)}
 * for details.
 * </li>
 * <li>
 * <b>begin</b> - integer value from which the iteration over collection will be
 * run. Default value is 0.
 * </li>
 * <li>
 * <b>end</b> - integer value to which the iteration over collection will be
 * run. Default value is number_of_elements_in_collection - 1 (or -1 if no
 * collection is supplied or it's <code>null</code>).
 * </li>
 * <li>
 * <b>step</b> - integer value that will be added to index on each iteration.
 * Must be positive. Default value is 1.
 * </li>
 * <li>
 * <b>var</b> - name of scope variable that will accept object that corresponds
 * to current iteration. If not specified, current object is not exported.
 * </li>
 * <li>
 * <b>scope</b> - scope of variable to export current object
 * </li>
 * <li>
 * <b>statusVar</b> - name of scope variable that will accept status object that
 * describes current iteration (see {@link com.blandware.atleap.webapp.taglib.core.logic.IteratorTag.IterationStatus})
 * </li>
 * <li>
 * <b>statusScope</b> - scope variable to export current iteration status
 * </li>
 * </ul>
 * </p>
 * <p>
 * Here is an example of this tag usage:
 * <pre>
 * &lt;ul&gt;
 * &lt;atleap:iterator items=${listOfStrings} var="currentString"&gt;
 *     &lt;li&gt;${currentString}&lt;/li&gt;
 * &lt;/atleap:iterator&gt;
 * &lt;/ul&gt;
 * </pre>
 * This code snippet will generate an HTML-list from list of strings which is
 * stored in scope under 'listOfStrings' name.
 * </p>
 * <p>
 * Here's another example:
 * <pre>
 * &lt;atleap:iterator begin="1" end="15" step="2" var="currentOdd"&gt;
 *     ${currentOdd}
 * &lt;/atleap:iterator&gt;
 * </pre>
 * This will generate a sequence of odd numbers from 1 to 15.
 * </p>
 * <p><a href="IteratorTag.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.5 $ $Date: 2006/08/03 10:08:24 $
 * @jsp.tag name="iterator"
 * body-content="scriptless"
 */
public class IteratorTag extends SimpleTagSupport {

	/**
	 * Collection of items we will run over. If not specified, array of integer indices will be created and iterated over
	 */
	protected Object items;

	/**
	 * Items object converted to the instance of <code>java.util.List</code>
	 */
	protected List itemsList;

	/**
	 * Zero-based number of element in collection to run from. Default value is 0
	 */
	protected Integer begin;

	/**
	 * Number of element in collection to run to. Default value is equal to (size of collection - 1)
	 */
	protected Integer end;

	/**
	 * Step of each iteration. Default value is 1
	 */
	protected Integer step;

	/**
	 * Name of variable to export result of each iteration
	 */
	protected String var;

	/**
	 * Scope to export result of each iteration to. Default value is "page"
	 */
	protected String scope = "page";

	/**
	 * Name of variable to export status of each iteration
	 */
	protected String statusVar;

	/**
	 * Scope to export status of each iteration to. Default value is "page"
	 */
	protected String statusScope = "page";

	/**
	 * Status of current iteration
	 */
	protected IterationStatus status;

	/**
	 * Creates new instance of IterateTag
	 */
	public IteratorTag() {
	}

	/**
     * Returns items
     *
     * @return items
     * @see #items
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Object"
	 * description="Collection of items to iterate over"
	 */
	public Object getItems() {
		return items;
	}

    /**
     * Sets items
     *
     * @param items items to set
     * @see #items
     */
	public void setItems(Object items) {
		this.items = items;
	}

	/**
     * Returns beginning number
     *
     * @return beginning number
     * @see #begin
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Integer"
	 * description="Zero-based number of element in collection to run from. Default value is 0"
	 */
	public Integer getBegin() {
		return begin;
	}

    /**
     * Sets beginning number
     *
     * @param begin beginning number to set
     * @see #begin
     */
	public void setBegin(Integer begin) {
		this.begin = begin;
	}

	/**
     * Returns ending number
     *
     * @return ending number
     * @see #end
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Integer"
	 * description="Number of element in collection to run to. Default value is equal to (size of collection - 1)"
	 */
	public Integer getEnd() {
		return end;
	}

    /**
     * Sets ending number
     *
     * @param end ending number to set
     * @see #end
     */
	public void setEnd(Integer end) {
		this.end = end;
	}

	/**
     * Returns step
     *
     * @return step
     * @see #step
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Integer"
	 * description="Step of each iteration. Default value is 1"
	 */
	public Integer getStep() {
		return step;
	}

    /**
     * Sets step
     *
     * @param step step to set
     * @see #step
     */
	public void setStep(Integer step) {
		this.step = step;
	}

	/**
     * Returns variable name
     *
     * @return variable name
     * @see #var
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of variable to export result of each iteration"
	 */
	public String getVar() {
		return var;
	}

    /**
     * Sets variable name
     *
     * @param var variable name to set
     * @see #var
     */
	public void setVar(String var) {
		this.var = var;
	}

	/**
     * Returns variable scope
     *
     * @return variable scope
     * @see #scope
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Scope to export result of each iteration to. Default value is 'page'"
	 */
	public String getScope() {
		return scope;
	}

    /**
     * Sets variable scope
     *
     * @param scope variable scope to set
     * @see #scope
     */
	public void setScope(String scope) {
		this.scope = scope;
	}

	/**
     * Returns name of status variable used to obtain some additional info about
     * each iteration
     *
     * @return name of status variable
     * @see #statusVar
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of variable to export status of each iteration"
	 */
	public String getStatusVar() {
		return statusVar;
	}

    /**
     * Sets name of status var used to obtain some additional info about each
     * iteration
     *
     * @param statusVar name of status variable to set
     * @see #statusVar
     */
	public void setStatusVar(String statusVar) {
		this.statusVar = statusVar;
	}

	/**
     * Returns status variable scope
     *
     * @return status variable scope
	 * @jsp.attribute required="false"
     * @see #statusScope
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Scope to export status of each iteration to. Default value is 'page'"
	 */
	public String getStatusScope() {
		return statusScope;
	}

    /**
     * Sets status variable scope
     *
     * @param statusScope status variable scope to set
     * @see #statusScope
     */
	public void setStatusScope(String statusScope) {
		this.statusScope = statusScope;
	}

	/**
	 * Returns status of current iteration
	 *
	 * @return iteration status
	 */
	public IterationStatus getStatus() {
		return status;
	}

	/**
	 * Iterates over specified collection and exposes results to the specified scope
	 *
	 * @throws JspException
	 * @throws IOException
	 */
	public void doTag() throws JspException, IOException {

		PageContext pageContext = (PageContext) getJspContext();

		// convert specified collection to list
		itemsList = convertItemsToList();

		// initialize indices and step
		int begin = this.begin != null ? this.begin.intValue() : 0;
		int end = this.end != null ? this.end.intValue() : itemsList.size() - 1;
		int step = this.step != null ? this.step.intValue() : 1;

        if (step <= 0) {
            throw new JspException("Step must be positive");
        }

		// obtain JSP body
		JspFragment body = getJspBody();
		if ( body == null ) {
			return;
		}

		// initialize status variable
		status = new IterationStatus(this.begin, this.end, this.step);

		// iterate over list

		// count of current iteration
		int count = 1;
		for ( int i = begin; i <= end; i += step, count++ ) {
			Object current = (Object) itemsList.get(i);

			// set current object
			status.setCurrent(current);

			// set count
			status.setCount(count);

			// set index
			status.setIndex(i);

			// expose current object to the specified scope
			TagUtils tagUtils = TagUtils.getInstance();
			if ( var != null ) {
				int varScope = PageContext.PAGE_SCOPE;
				if ( scope != null ) {
					varScope = tagUtils.getScope(scope);
				}
				pageContext.setAttribute(var, current, varScope);
			}

			// expose status to the specified scope
			if ( statusVar != null ) {
				int statusVarScope = PageContext.PAGE_SCOPE;
				if ( statusScope != null ) {
					statusVarScope = tagUtils.getScope(statusScope);
				}
				pageContext.setAttribute(statusVar, status, statusVarScope);
			}

			// invoke body content
			body.invoke(null);
		}
	}

	/**
	 * Converts specified collection to the instance of <code>java.util.List</code>
	 */
	protected List convertItemsToList() {
		List list = new ArrayList();
		if ( items == null ) {
			// create array of indices
			int begin = this.begin != null ? this.begin.intValue() : 0;
			int end = this.end != null ? this.end.intValue() : -1;
			for ( int i = 0; i <= end; i++ ) {
				list.add(new Integer(i));
			}
		} else {
			list = ConvertUtil.convertCollectionToList(items);
		}
		return list;
	}

	/**
	 * <p>Exposes the current status of
	 * an iteration. IteratorTag provides a mechanism to
	 * return information about the current index of the iteration and
	 * convenience methods to determine whether or not the current round is
	 * either the first or last in the iteration.  It also lets authors
	 * use the status object to obtain information about the iteration range,
	 * step, and current object.</p>
	 * <p>Interface LoopTagStatus has initially been created by Shawn Bayern.
	 * This has the same functionality and has been created to meet our conditions by Sergey Zubtsovskiy</p>
	 *
	 * @author Shawn Bayern (JSTL team)
	 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
	 */
	public class IterationStatus implements Serializable {

		/**
		 * Begin index of iteration
		 */
		protected Integer begin;

		/**
		 * End index of iteration
		 */
		protected Integer end;

		/**
		 * Step of each iteration;
		 */
		protected Integer step;

		/**
		 * Index of the current round of the iteration
		 */
		protected int index;

		/**
		 * "Count" of the current round of the iteration.  The
		 * count is a relative, 1-based sequence number identifying the
		 * current "round" of iteration (in context with all rounds the
		 * current iteration will perform)
		 */
		protected int count;

		/**
		 * Current item in the iteration
		 */
		protected Object current;

		/**
		 * Creates new instance of iteration status
		 * @param begin Begin index of iteration
		 * @param end End index of iteration
		 * @param step Step of each iteration
		 */
		public IterationStatus(Integer begin, Integer end, Integer step) {
			this.begin = begin;
			this.end = end;
			this.step = step;
		}

		/**
		 * Retrieves the index of the current round of the iteration.  If
		 * iteration is being performed over a subset of an underlying
		 * array, java.lang.Collection, or other type, the index returned
		 * is absolute with respect to the underlying collection.  Indices
		 * are 0-based.
		 *
		 * @return the 0-based index of the current round of the iteration
		 */
		public int getIndex() {
			return index;
		}

		/**
		 * Package-private setter used in parent class of this one
		 * @param index Index to set
		 */
		void setIndex(int index) {
			this.index = index;
		}

		/**
		 * <p>Retrieves the "count" of the current round of the iteration.  The
		 * count is a relative, 1-based sequence number identifying the
		 * current "round" of iteration (in context with all rounds the
		 * current iteration will perform).</p>
		 *
		 * <p>As an example, an iteration with begin = 5, end = 15, and step =
		 * 5 produces the counts 1, 2, and 3 in that order.</p>
		 *
		 * @return the 1-based count of the current round of the iteration
		 */
		public int getCount() {
			return count;
		}

		/**
		 * Package-private setter used in parent class of this one
		 * @param count Count of current iteration
		 */
		void setCount(int count) {
			this.count = count;
		}

		/**
		 * Retrieves the current item in the iteration.  Behaves
		 * idempotently; calling getCurrent() repeatedly should return the same
		 * Object until the iteration is advanced.  (Specifically, calling
		 * getCurrent() does <b>not</b> advance the iteration.)
		 *
		 * @return the current item as an object
		 */
		public Object getCurrent() {
			return current;
		}


		/**
		 * Package-private setter used in parent class of this one
		 * @param current Object exposed on current iteration
		 */
		void setCurrent(Object current) {
			this.current = current;
		}

		/**
		 * Returns the value of the 'begin' attribute for the associated tag,
		 * or null if no 'begin' attribute was specified.
		 *
		 * @return the 'begin' value for the associated tag, or null
		 * if no 'begin' attribute was specified
		 */
		public Integer getBegin() {
			return begin;
		}

		/**
		 * Returns the value of the 'end' attribute for the associated tag,
		 * or null if no 'end' attribute was specified.
		 *
		 * @return the 'end' value for the associated tag, or null
		 * if no 'end' attribute was specified
		 */
		public Integer getEnd() {
			return end;
		}

		/**
		 * Returns the value of the 'step' attribute for the associated tag,
		 * or null if no 'step' attribute was specified.
		 *
		 * @return the 'step' value for the associated tag, or null
		 * if no 'step' attribute was specified
		 */
		public Integer getStep() {
			return step;
		}

		/**
		 * Returns information about whether the current round of the
		 * iteration is the first one.  This current round may be the 'first'
		 * even when getIndex() != 0, for 'index' refers to the absolute
		 * index of the current 'item' in the context of its underlying
		 * collection.  It is always that case that a true result from
		 * isFirst() implies getCount() == 1.
		 *
		 * @return <tt>true</tt> if the current round is the first in the
		 * iteration, <tt>false</tt> otherwise.
		 */
		public boolean isFirst() {
			int begin = this.begin != null ? this.begin.intValue() : 0;
			return index == begin;
		}

		/**
		 * Returns information about whether the current round of the
		 * iteration is the last one.  As with isFirst(), subsetting is
		 * taken into account.  isLast() doesn't necessarily refer to the
		 * status of the underlying Iterator; it refers to whether or not
		 * the current round will be the final round of iteration for the
		 * tag associated with this LoopTagStatus.
		 *
		 * @return <tt>true</tt> if the current round is the last in the
		 * iteration, <tt>false</tt> otherwise.
		 */
		public boolean isLast() {
			int end = this.end != null ? this.end.intValue() : itemsList.size() - 1;
			int step = this.step != null ? this.step.intValue() : 1;
			return index + step > end;
		}

	}

}
